package game.map;

import static org.lwjgl.opengl.GL11.*;

import java.io.Serializable;

import application.Configuration;
import application.messages.Message;
import application.messages.commands.ConsoleCommand;
import application.messages.commands.GameCommand;

import data.DataManager;
import data.DataManager.BlockType;
import game.Inventory;
import game.entities.players.Player;
import game.graphics.Block;
import game.graphics.Fire;
import game.graphics.Light;
import game.map.generator.Generator;
import helpers.BlockTypeInfo;

/**
 * Used for rendering, drawing and manipulating with map.
 * 
 * @author Tomas Zima
 */
public class Map implements Serializable
{
	/**
	 * Constructor.
	 * Create map using generator.
	 */
	public Map()
	{
		this.mapWidth	= 4096;
		this.mapHeight	= 1024;
		
		this.blocks		= new Block[mapWidth][mapHeight];
		
		Generator generator = new Generator(blocks);
		generator.generate();
	}
	
	/**
	 * Redraw map.
	 * 
	 * @param centerX
	 * 	Coordinate of camera in the X axis.
	 * 
	 * @param centerY
	 * 	Coordinate of camera in the Y axis.
	 */
	public void redraw(int centerX, int centerY)
	{
		// Save last coordinates of camera
		this.lastCameraX = centerX;
		this.lastCameraY = centerY;
		
		// How many of blocks can be drawn into the window?
		int screenWidth		= windowWidth / Configuration.BLOCK_SIZE;
		int screenHeight	= windowHeight / Configuration.BLOCK_SIZE;
		
		// Coordinates of block, which is under camera
		int centerBlockX	= centerX / Configuration.BLOCK_SIZE;
		int centerBlockY	= centerY / Configuration.BLOCK_SIZE;
		
		// Where the drawing begins...
		int startX 			= centerBlockX - screenWidth / 2;
		int startY			= centerBlockY - screenHeight / 2;
		
		// Where the drawing ends...
		int endX			= centerBlockX + screenWidth / 2;
		int endY			= centerBlockY + screenHeight / 2;
		
		// Calculate translation: 
		int transX = centerX % Configuration.BLOCK_SIZE;
		int transY = centerY % Configuration.BLOCK_SIZE;
		
		/*
		 * If camera is on right side of screen, we
		 * want to move whole scene to left.
		 */
		if (centerX > windowWidth / 2)
		{
			transX *= -1;
		}
		
		/*
		 * If camera is on down side of screen, we
		 * want to move whole scene up. 
		 */
		if (centerY > windowHeight / 2)
		{
			transY *= -1;
		}

		// Save current matrix to stack and move the scene
		
		glPushMatrix();
		glTranslatef(transX, transY, 0);
		
		// Redraw all visible blocks, including the blocks which are
		// only partly visible 
		for (int i = startX - 1; i < endX + 1; i++)
		{
			for (int j = startY - 1; j < endY + 1; j++)
			{
				if (i > 0 && i < mapWidth && j > 0 && j < mapHeight)
				{
					blocks[i][j].redraw((i - startX) * Configuration.BLOCK_SIZE, (j - startY) * Configuration.BLOCK_SIZE);
				}
			}
		}
		
		// Restore matrix
		glPopMatrix();
		
		//new Light(blocks, Configuration.WINDOW_WIDTH, Configuration.WINDOW_HEIGHT).redraw(centerX, centerY, lightX, lightY, 5, 0.8f, 0.06f);
		//fire.redraw(centerX, centerY);
	}
	
	// testing purposes
	private int lightX = 2048;
	private int lightY = 512;
	//private Fire fire = new Fire(2048 * 32, 512 * 32, 400, 200);
	
	public void destroyBlock(int x, int y)
	{
		try
		{
			if (blocks[x][y].getBlockType() != DataManager.BlockType.BLOCK_BEDROCK)
			{
				switch (blocks[x][y].getBlockType())
				{
					case BLOCK_CACTUS:
					case BLOCK_CACTUS_BREAK:
					case BLOCK_CACTUS_TOP:
					case BLOCK_CACTUS_TOP_BREAK:
					case BLOCK_COAL:
					case BLOCK_COAL_BREAK:
					case BLOCK_DIAMOND:
					case BLOCK_DIAMOND_BREAK:
					case BLOCK_DIRT:
					case BLOCK_DIRT_BREAK:
					case BLOCK_FLOWER_RED:
					case BLOCK_FLOWER_YELLOW:
					case BLOCK_GOLD:
					case BLOCK_GOLD_BREAK:
					case BLOCK_GRASS:
					case BLOCK_GRASS_BREAK:
					case BLOCK_IRON:
					case BLOCK_IRON_BREAK:
					case BLOCK_SAMPLING:
					case BLOCK_SAND:
					case BLOCK_SAND_BREAK:
					case BLOCK_SHROOM_BROWN:
					case BLOCK_SHROOM_RED:
					case BLOCK_STONE:
					case BLOCK_STONE_BREAK:
						if (BlockTypeInfo.getBrokenBlockType(blocks[x][y].getBlockType()) == BlockType.BLOCK_NONE)
						{
							Inventory.getInstance().addItem(blocks[x][y].getBasicBlockType());
						}
						break;
						
					default:
						break;
					//
				}
				blocks[x][y].destroy();
			}
		}
		catch (ArrayIndexOutOfBoundsException errHandler)
		{
			//
		}
	}
	
	public void addNewBlock(int x, int y, DataManager.BlockType type)
	{
		try
		{
			blocks[x][y].setBlockType(type);
			lightX = x;
			lightY = y;
		}
		catch (ArrayIndexOutOfBoundsException errHandler)
		{
			//
		}
	}
	
	public void enterNewMessage(Message message)
	{
		switch ((GameCommand) message.command)
		{
			default:
				break;
		}
	}
	
	public void enterNewConsoleCommand(Message message)
	{
		switch ((ConsoleCommand) message.command)
		{
			case EXPLODE_COLUMN:
				if (message.value == 0)
				{
					for (int i = 0; i < mapHeight; i++)
					{
						if (this.blocks[lastCameraX / 32][i].getBlockType() != DataManager.BlockType.BLOCK_BEDROCK)
						{
							this.blocks[lastCameraX / 32][i].setBlockType(BlockType.BLOCK_NONE);
						}
					}
				}
				else
				{
					for (int i = lastCameraY / 32 - message.value; i < lastCameraY / 32 + message.value; i++)
					{
						if (this.blocks[lastCameraX / 32][i].getBlockType() != DataManager.BlockType.BLOCK_BEDROCK)
						{
							this.blocks[lastCameraX / 32][i].setBlockType(BlockType.BLOCK_NONE);
						}
					}
				}
				
				break;
				
			case EXPLODE_COLUMN_DOWN:
				if (message.value == 0)
				{
					for (int i = lastCameraY / 32; i < mapHeight; i++)
					{
						if (this.blocks[lastCameraX / 32][i].getBlockType() != DataManager.BlockType.BLOCK_BEDROCK)
						{
							this.blocks[lastCameraX / 32][i].setBlockType(BlockType.BLOCK_NONE);
						}
					}
				}
				else
				{
					for (int i = lastCameraY / 32; i < lastCameraY / 32 + message.value; i++)
					{
						try
						{
							if (this.blocks[lastCameraX / 32][i].getBlockType() != DataManager.BlockType.BLOCK_BEDROCK)
							{
								this.blocks[lastCameraX / 32][i].setBlockType(BlockType.BLOCK_NONE);
							}
						}
						catch (ArrayIndexOutOfBoundsException errHandler)
						{
							//
						}
					}
				}
				
				break;
				
			case EXPLODE_COLUMN_UP:
				if (message.value == 0)
				{
					for (int i = lastCameraY / 32; i >= 0; i--)
					{
						if (this.blocks[lastCameraX / 32 + 1][i].getBlockType() != DataManager.BlockType.BLOCK_BEDROCK)
						{
							this.blocks[lastCameraX / 32 + 1][i].setBlockType(BlockType.BLOCK_NONE);
						}
					}
				}
				else
				{
					for (int i = lastCameraY / 32; i >= lastCameraY / 32 - message.value; i--)
					{
						if (this.blocks[lastCameraX / 32 + 1][i].getBlockType() != DataManager.BlockType.BLOCK_BEDROCK)
						{
							this.blocks[lastCameraX / 32 + 1][i].setBlockType(BlockType.BLOCK_NONE);
						}
					}
				}
				
				break;
			
			case REMOVE_ALL_BLOCKS:
				System.out.println("here");
				for (int i = 0; i < mapWidth; i++)
				{
					for (int j = 0; j < mapHeight; j++)
					{
						if (this.blocks[i][j].getBlockType() != BlockType.BLOCK_BEDROCK)
						{
							this.blocks[i][j].setBlockType(BlockType.BLOCK_NONE);
						}
					}
				}
				
				break;
				
			default:
				break;
		}
	}
	
	public final Block[][] getBlocksReferences()
	{
		return blocks;
	}

	// Memory for blocks
	private Block blocks[][];
	
	// Size of map
	private int mapWidth;
	private int mapHeight;	
	
	private final int windowWidth	= Configuration.WINDOW_WIDTH;
	private final int windowHeight	= Configuration.WINDOW_HEIGHT;
	
	private int lastCameraX;
	private int lastCameraY;
}
